/*
 * Copyright (c) 2005, 2020 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2005, 2015 SAP. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     SAP - initial API and implementation

package org.eclipse.persistence.testing.tests.wdf.jpa1.relation;

import static junit.framework.Assert.assertTrue;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import jakarta.persistence.Cache;
import jakarta.persistence.EntityManager;

import org.eclipse.persistence.testing.framework.wdf.Bugzilla;
import org.eclipse.persistence.testing.framework.wdf.JPAEnvironment;
import org.eclipse.persistence.testing.framework.wdf.ToBeInvestigated;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Bicycle;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.CostCenter;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Cubicle;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.CubiclePrimaryKeyClass;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Employee;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.MotorVehicle;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Project;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.TravelProfile;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Vehicle;
import org.eclipse.persistence.testing.tests.wdf.jpa1.JPA1Base;
import org.junit.Test;

/**
 * Testing relations in combination with object cache. Test cases generated by all-pairs testing tool: <li>ToManyFK, eager,
 * owning side right, cacheable both: testCostCenterEmployee</li> <li>ToOneFK, lazy, owning side left, cacheable right:
 * testMotorVehicleEmployee</li> <li>ToOneFK, eager, owning side right, cacheable both: testCubicleEmployee</li> <li>
 * ToManyJoinTable, eager, owning side left, cacheable right: testBicycleEmployee</li> <li>ToManyJoinTable, lazy, owning side
 * left, cacheable both: testEmployeeProject</li> <li>ToManyFK, lazy, owning side right, cacheable right: n.a.</li> <li>
 * ToManyJoinTable, lazy, owning side right, cacheable right: testTravelProfileVehicle</li>
 *
 *
 *
 * <li>ToManyJoinTable, lazy, owning side right, cacheable both: testProjectEmployee</li> <li>ToManyJoinTable, lazy, owning side
 * left, cacheable left: testEmployeePatent</li> <li>ToOneFK, eager, owning side left, cacheable left: testEmployeeTravelProfile
 * </li> <li>ToManyFK, eager, owning side right, cacheable right</li> </ul>
 */
public class TestRelationshipsWithCache extends JPA1Base {

    /**
     * Test case: CostCenter.employees
     * <p>
     * Relationship mapping: ToManyFK, FetchType: eager, owning side: right, cacheable: both
     */
    @Test
    @Bugzilla(bugid=309681)
    public void testCostCenterEmployee() {
        final JPAEnvironment env = getEnvironment();
        final EntityManager em = env.getEntityManager();
        final Cache cache = em.getEntityManagerFactory().getCache();

        int costCenterId = 1;
        CostCenter costCenter = new CostCenter(1, "lotsOfMoney");
        int employeeId1 = 1;
        int employeeId2 = 2;
        Employee employee1 = new Employee(employeeId1, "very", "expensive", null);
        Employee employee2 = new Employee(employeeId2, "even", "moreExpensive", null);
        costCenter.addEmployee(employee1);
        costCenter.addEmployee(employee2);
        employee1.setCostCenter(costCenter);
        employee2.setCostCenter(costCenter);
        try {
            env.beginTransaction(em);
            em.persist(costCenter);
            em.persist(employee1);
            em.persist(employee2);
            env.commitTransactionAndClear(em);

            em.find(CostCenter.class, costCenterId); // eager loading of Employees
            assertTrue(cache.contains(CostCenter.class, costCenterId));
            assertTrue(cache.contains(Employee.class, employeeId1));
            assertTrue(cache.contains(Employee.class, employeeId2));

            env.beginTransaction(em);
            costCenter = em.find(CostCenter.class, costCenterId);
            employee1 = em.find(Employee.class, employeeId1);
            employee2 = em.find(Employee.class, employeeId2);
            List<Employee> foundEmployees = costCenter.getEmployees();
            assertTrue(foundEmployees != null);
            assertTrue("wrong number of riders: " + foundEmployees.size(), foundEmployees.size() == 2);
            if (foundEmployees.get(0).getId() == employeeId1) {
                assertTrue("wrong rider: " + foundEmployees.get(1).getId(), foundEmployees.get(1).getId() == employeeId2);
            } else {
                assertTrue("wrong rider: " + foundEmployees.get(0).getId(), foundEmployees.get(0).getId() == employeeId2);
                assertTrue("wrong rider: " + foundEmployees.get(1).getId(), foundEmployees.get(1).getId() == employeeId1);
            }
            CostCenter costCenter1 = employee1.getCostCenter();
            CostCenter costCenter2 = employee2.getCostCenter();
            assertTrue(costCenter1 != null);
            assertTrue(costCenter1.getId() == costCenterId);
            assertTrue(costCenter2 != null);
            assertTrue(costCenter2.getId() == costCenterId);
            costCenter.setEmployees(null);
            employee1.setCostCenter(null);
            employee2.setCostCenter(null);
            env.commitTransactionAndClear(em);

            assertTrue(!cache.contains(CostCenter.class, costCenterId));
            assertTrue(!cache.contains(Employee.class, employeeId1));
            assertTrue(!cache.contains(Employee.class, employeeId2));
            costCenter = em.find(CostCenter.class, costCenterId);
            employee1 = em.find(Employee.class, employeeId1);
            employee2 = em.find(Employee.class, employeeId2);
            assertTrue(costCenter.getEmployees().size() == 0);
            assertTrue(employee1.getCostCenter() == null);
            assertTrue(employee2.getCostCenter() == null);
        } finally {
            closeEntityManager(em);
        }
    }

    /**
     * Test case: MotorVehicle.driver
     * <p>
     * Relationship mapping: ToOneFK, FetchType: lazy, owning side: left, cacheable: right
     */
    @Test
    @Bugzilla(bugid=309681)
    public void testMotorVehicleEmployee() {
        final JPAEnvironment env = getEnvironment();
        final EntityManager em = env.getEntityManager();
        final Cache cache = em.getEntityManagerFactory().getCache();
        final boolean isLazyToOneEnabled = false;

        MotorVehicle motorVehicle = new MotorVehicle();
        int employeeId = 11;
        Employee employee = new Employee(employeeId, "first", "driver", null);
        motorVehicle.setDriver(employee);
        employee.setMotorVehicle(motorVehicle);
        try {
            env.beginTransaction(em);
            em.persist(motorVehicle);
            em.persist(employee);
            env.commitTransactionAndClear(em);
            Short motorVehicleId = motorVehicle.getId();

            em.find(MotorVehicle.class, motorVehicleId);
            if (isLazyToOneEnabled) {
                assertTrue(!cache.contains(Employee.class, employeeId));
                employee = em.find(Employee.class, employeeId);
                // assertTrue(employee instanceof LazilyLoadable); FIXME
                // assertTrue( ((LazilyLoadable) employee)._isPending() ); FIXME
            } else {
                assertTrue(cache.contains(Employee.class, employeeId));
            }

            env.beginTransaction(em);
            motorVehicle = em.find(MotorVehicle.class, motorVehicleId);
            employee = em.find(Employee.class, employeeId);
            assertTrue(employeeId == motorVehicle.getDriver().getId());
            assertTrue(motorVehicleId.equals(employee.getMotorVehicle().getId()));
            assertTrue(employee == motorVehicle.getDriver());
            motorVehicle.setDriver(null);
            employee.setMotorVehicle(null);
            env.commitTransactionAndClear(em);

            assertTrue(!cache.contains(Employee.class, employeeId));
            motorVehicle = em.find(MotorVehicle.class, motorVehicleId);
            employee = em.find(Employee.class, employeeId);
            assertTrue(motorVehicle.getDriver() == null);
            assertTrue(employee.getMotorVehicle() == null);
        } finally {
            closeEntityManager(em);
        }
    }

    /**
     * Test case: Employee.cubicle
     * <p>
     * Relationship mapping: ToOneFK, FetchType: eager, owning side: right, cacheable: both
     */
    @Test
    @Bugzilla(bugid=309681)
    public void testCubicleEmployee() {
        final JPAEnvironment env = getEnvironment();
        final EntityManager em = env.getEntityManager();
        final Cache cache = em.getEntityManagerFactory().getCache();

        int employeeId = 21;
        Employee employee = new Employee(employeeId, "first", "last", null);
        CubiclePrimaryKeyClass cubicleId = new CubiclePrimaryKeyClass(1, 2);
        Cubicle cubicle = new Cubicle(cubicleId, "red", employee);
        employee.setCubicle(cubicle);
        try {
            env.beginTransaction(em);
            em.persist(employee);
            em.persist(cubicle);
            env.commitTransactionAndClear(em);

            em.find(Cubicle.class, cubicleId);
            assertTrue(cache.contains(Cubicle.class, cubicleId));
            assertTrue(cache.contains(Employee.class, employeeId));

            env.beginTransaction(em);
            cubicle = em.find(Cubicle.class, cubicleId);
            employee = em.find(Employee.class, employeeId);
            assertTrue("cubicle has wrong employee: " + cubicle.getEmployee().getId(), employeeId == cubicle.getEmployee()
                    .getId());
            assertTrue("employee has wrong cubicle: " + employee.getCubicle().getId(), cubicleId.equals(employee.getCubicle()
                    .getId()));
            cubicle.setEmployee(null);
            employee.setCubicle(null);
            env.commitTransactionAndClear(em);

            assertTrue(!cache.contains(Cubicle.class, cubicleId));
            assertTrue(!cache.contains(Employee.class, employeeId));
            cubicle = em.find(Cubicle.class, cubicleId);
            employee = em.find(Employee.class, employeeId);
            assertTrue(cubicle.getEmployee() == null);
            assertTrue(employee.getCubicle() == null);
        } finally {
            closeEntityManager(em);
        }
    }

    /**
     * Test case: Bicycle.employees
     * <p>
     * Relationship mapping: ToManyJoinTable, FetchType: eager, owning side: left, cacheable: right
     */
    @Test
    @Bugzilla(bugid=309681)
    public void testBicycleEmployee() {
        final JPAEnvironment env = getEnvironment();
        final EntityManager em = env.getEntityManager();
        final Cache cache = em.getEntityManagerFactory().getCache();

        Bicycle bicycle = new Bicycle();
        int employeeId1 = 31;
        int employeeId2 = 32;
        Employee employee1 = new Employee(employeeId1, "good", "rider1", null);
        Employee employee2 = new Employee(employeeId2, "bad", "rider2", null);
        employee1.addBicycle(bicycle); // maintains also bicycle.riders relationship
        employee2.addBicycle(bicycle);
        try {
            env.beginTransaction(em);
            em.persist(bicycle);
            em.persist(employee1);
            em.persist(employee2);
            env.commitTransactionAndClear(em);
            Short bicycleId = bicycle.getId();

            em.find(Bicycle.class, bicycleId); // eager loading of Employees
            assertTrue(cache.contains(Employee.class, employeeId1));
            assertTrue(cache.contains(Employee.class, employeeId2));

            env.beginTransaction(em);
            bicycle = em.find(Bicycle.class, bicycleId);
            employee1 = em.find(Employee.class, employeeId1);
            employee2 = em.find(Employee.class, employeeId2);
            Collection<Employee> foundRiders = bicycle.getRiders();
            assertTrue(foundRiders != null);
            assertTrue("wrong number of riders: " + foundRiders.size(), foundRiders.size() == 2);
            Employee[] ridersArray = foundRiders.toArray(new Employee[2]);
            if (ridersArray[0].getId() == employeeId1) {
                assertTrue("wrong rider: " + ridersArray[1].getId(), ridersArray[1].getId() == employeeId2);
            } else {
                assertTrue("wrong rider: " + ridersArray[0].getId(), ridersArray[0].getId() == employeeId2);
                assertTrue("wrong rider: " + ridersArray[1].getId(), ridersArray[1].getId() == employeeId1);
            }
            Set<Bicycle> bicycles1 = employee1.getBicycles();
            Set<Bicycle> bicycles2 = employee2.getBicycles();
            assertTrue(bicycles1 != null);
            assertTrue(bicycles2 != null);
            assertTrue(bicycles1.size() == 1);
            assertTrue(bicycles2.size() == 1);
            assertTrue(bicycleId.equals(bicycles1.toArray(new Bicycle[1])[0].getId()));
            assertTrue(bicycleId.equals(bicycles2.toArray(new Bicycle[1])[0].getId()));
            bicycle.setRiders(null);
            employee1.setBicycles(null);
            employee2.setBicycles(null);
            env.commitTransactionAndClear(em);

            assertTrue(!cache.contains(Employee.class, employeeId1));
            assertTrue(!cache.contains(Employee.class, employeeId2));
            bicycle = em.find(Bicycle.class, bicycleId);
            employee1 = em.find(Employee.class, employeeId1);
            employee2 = em.find(Employee.class, employeeId2);
            assertTrue(bicycle.getRiders().size() == 0);
            assertTrue(employee1.getBicycles().size() == 0);
            assertTrue(employee2.getBicycles().size() == 0);
        } finally {
            closeEntityManager(em);
        }
    }

    /**
     * Test case: Employee.projects
     * <p>
     * Relationship mapping: ToManyJoinTable, FetchType: lazy, owning side: left, cacheable: both
     */
    @SuppressWarnings("unchecked")
    @Test
    @Bugzilla(bugid=309681)
    public void testEmployeeProject() {
        final JPAEnvironment env = getEnvironment();
        final EntityManager em = env.getEntityManager();
        final Cache cache = em.getEntityManagerFactory().getCache();

        int employeeId = 41;
        Employee employee = new Employee(employeeId, "aaa", "member", null);
        Project project1 = new Project("evaluation");
        Project project2 = new Project("demotivation");
        project1.addEmployee(employee); // maintains also employee.projects relationship
        project2.addEmployee(employee);
        try {
            env.beginTransaction(em);
            em.persist(employee);
            em.persist(project1);
            em.persist(project2);
            env.commitTransactionAndClear(em);
            Integer projectId1 = project1.getId();
            Integer projectId2 = project2.getId();

            env.beginTransaction(em);
            employee = em.find(Employee.class, employeeId);
            assertTrue(cache.contains(Employee.class, employeeId));
            assertTrue(!cache.contains(Project.class, projectId1)); // lazy loading of projects
            assertTrue(!cache.contains(Project.class, projectId2));
            employee.getProjects().size();
            assertTrue(cache.contains(Project.class, projectId1));
            assertTrue(cache.contains(Project.class, projectId2));
            project1 = em.find(Project.class, projectId1);
            project2 = em.find(Project.class, projectId2);
            Set<Project> foundProjects = employee.getProjects();
            assertTrue(foundProjects != null);
            assertTrue("wrong number of projects: " + foundProjects.size(), foundProjects.size() == 2);
            Project[] projArray = foundProjects.toArray(new Project[2]);
            if (projArray[0].getId().equals(projectId1)) {
                assertTrue("wrong project: " + projArray[1].getId(), projArray[1].getId().equals(projectId2));
            } else {
                assertTrue("wrong project: " + projArray[0].getId(), projArray[0].getId().equals(projectId2));
                assertTrue("wrong project: " + projArray[1].getId(), projArray[1].getId().equals(projectId1));
            }
            Set<Employee> employees1 = project1.getEmployees();
            Set<Employee> employees2 = project2.getEmployees();
            assertTrue(employees1 != null);
            assertTrue(employees2 != null);
            assertTrue(employees1.size() == 1);
            assertTrue(employees2.size() == 1);
            assertTrue(employees1.toArray(new Employee[1])[0].getId() == employeeId);
            assertTrue(employees2.toArray(new Employee[1])[0].getId() == employeeId);
            employee.setProjects(null);
            project1.setEmployees(null);
            project2.setEmployees(null);
            env.commitTransactionAndClear(em);

            assertTrue(!cache.contains(Employee.class, employeeId));
            assertTrue(!cache.contains(Project.class, projectId1));
            assertTrue(!cache.contains(Project.class, projectId2));
            employee = em.find(Employee.class, employeeId);
            project1 = em.find(Project.class, projectId1);
            project2 = em.find(Project.class, projectId2);
            assertTrue(employee.getProjects().size() == 0);
            assertTrue(project1.getEmployees().size() == 0);
            assertTrue(project2.getEmployees().size() == 0);
        } finally {
            closeEntityManager(em);
        }
    }

    /**
     * Test case: TravelProfile.vehicles
     * <p>
     * Relationship mapping: ToManyJoinTable, FetchType: lazy, owning side: right, cacheable: right
     */
    @SuppressWarnings("unchecked")
    @Test
    @ToBeInvestigated
    public void testTravelProfileVehicle() { // TODO remove
        final JPAEnvironment env = getEnvironment();
        final EntityManager em = env.getEntityManager();
        final Cache cache = em.getEntityManagerFactory().getCache();

        byte[] travelProfileId = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
        TravelProfile travelProfile = new TravelProfile(travelProfileId, true, "neverComeBack");
        Vehicle vehicle1 = new Vehicle();
        Vehicle vehicle2 = new Vehicle();
        vehicle1.addProfile(travelProfile); // maintains also travelProfile.vehicles relationship
        vehicle2.addProfile(travelProfile);
        try {
            env.beginTransaction(em);
            em.persist(travelProfile);
            em.persist(vehicle1);
            em.persist(vehicle2);
            env.commitTransactionAndClear(em);
            Short vehicleId1 = vehicle1.getId();
            Short vehicleId2 = vehicle2.getId();

            env.beginTransaction(em);
            travelProfile = em.find(TravelProfile.class, travelProfileId);
            assertTrue(!cache.contains(Vehicle.class, vehicleId1)); // lazy loading of vehicles
            assertTrue(!cache.contains(Vehicle.class, vehicleId2));
            travelProfile.getVehicles().size();
            assertTrue(cache.contains(Vehicle.class, vehicleId1));
            assertTrue(cache.contains(Vehicle.class, vehicleId2));
            vehicle1 = em.find(Vehicle.class, vehicleId1);
            vehicle2 = em.find(Vehicle.class, vehicleId2);
            Set<Vehicle> foundVehicles = travelProfile.getVehicles();
            assertTrue(foundVehicles != null);
            assertTrue("wrong number of vehicles: " + foundVehicles.size(), foundVehicles.size() == 2);
            Vehicle[] vehicleArray = foundVehicles.toArray(new Vehicle[2]);
            if (vehicleArray[0].getId().equals(vehicleId1)) {
                assertTrue("wrong vehicle: " + vehicleArray[1].getId(), vehicleArray[1].getId().equals(vehicleId2));
            } else {
                assertTrue("wrong vehicle: " + vehicleArray[0].getId(), vehicleArray[0].getId().equals(vehicleId2));
                assertTrue("wrong vehicle: " + vehicleArray[1].getId(), vehicleArray[1].getId().equals(vehicleId1));
            }
            Set<TravelProfile> travelProfiles1 = vehicle1.getProfiles();
            Set<TravelProfile> travelProfiles2 = vehicle2.getProfiles();
            assertTrue(travelProfiles1 != null);
            assertTrue(travelProfiles2 != null);
            assertTrue(travelProfiles1.size() == 1);
            assertTrue(travelProfiles2.size() == 1);
            assertTrue(Arrays.equals(travelProfiles1.toArray(new TravelProfile[1])[0].getGuid(), travelProfileId));
            assertTrue(Arrays.equals(travelProfiles2.toArray(new TravelProfile[1])[0].getGuid(), travelProfileId));
            travelProfile.setVehicles(null);
            vehicle1.setProfiles(null);
            vehicle2.setProfiles(null);
            env.commitTransactionAndClear(em);

            assertTrue(!cache.contains(Vehicle.class, vehicleId1));
            assertTrue(!cache.contains(Vehicle.class, vehicleId2));
            travelProfile = em.find(TravelProfile.class, travelProfileId);
            vehicle1 = em.find(Vehicle.class, vehicleId1);
            vehicle2 = em.find(Vehicle.class, vehicleId2);
            assertTrue(travelProfile.getVehicles().size() == 0);
            assertTrue(vehicle1.getProfiles().size() == 0);
            assertTrue(vehicle2.getProfiles().size() == 0);
        } finally {
            closeEntityManager(em);
        }
    }

    @Test
    /**
     * just for the case that all other methods are skipped
     */
    public void dummyTestMethod() {
        return;
    }

}
